package com.hwtx.form.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import static com.hwtx.form.domain.FormConstants.TYPE_KEY;

@Slf4j
public class JacksonUtil {

    public static final ObjectMapper JSON_MAPPER = new ObjectMapper();

    static {
        JSON_MAPPER.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

        // Include.Include.ALWAYS 默认
        // Include.NON_DEFAULT 属性为默认值不序列化
        // Include.NON_EMPTY 属性为 空（“”） 或者为 NULL 都不序列化
        // Include.NON_NULL 属性为NULL 不序列化

        DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");

        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DATETIME_FORMATTER));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DATE_FORMATTER));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(TIME_FORMATTER));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATETIME_FORMATTER));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(TIME_FORMATTER));

        JSON_MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        JSON_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        JSON_MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        JSON_MAPPER.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
        JSON_MAPPER.registerModule(javaTimeModule);
        JSON_MAPPER.setTimeZone(TimeZone.getDefault());
    }

    public static void traverseNodes(JsonNode rootNode, String field, List<TraverseMatchRule> rules, FindNodeCallback callback) {
        List<JsonNode> apiNodes = rootNode.findParents(field);
        apiNodes.forEach(parent -> {
            if (parent.isObject()) {
                if (CollectionUtils.isNotEmpty(rules)) {
                    rules.forEach(rule -> {
                        String property = rule.getProperty();
                        if (!parent.has(property)) {
                            return;
                        }
                        if (rule.isObject()) {
                            nodeCallback(rootNode, parent.get(property), (ObjectNode) parent, property, callback);
                        } else {
                            nodeCallback(rootNode, parent, null, "", callback);
                        }
                    });
                } else {
                    nodeCallback(rootNode, parent, null, "", callback);
                }
            } else if (parent.isArray()) {
                throw new RuntimeException("父节点为数组节点不支持");
            }
        });
    }

    private static void nodeCallback(JsonNode rootNode, JsonNode node, ObjectNode parent, String property, FindNodeCallback callback) {
        if (node.isTextual()) {
            callback.handleTextNode((TextNode) node, parent, property);
        } else if (node.isObject()) {
            callback.handleObjectNode(rootNode, (ObjectNode) node, parent, property);
        } else if (node.isArray()) {
            ArrayNode arrayNode = (ArrayNode) node;
            for (int i = 0; i < arrayNode.size(); i++) {
                JsonNode child = arrayNode.get(i);
                callback.handleArrayNode(rootNode, arrayNode, child, i);
            }
        }
    }

    public static String object2json(Object obj, JsonInclude.Include include, boolean isPretty) {
        if (null != obj) {
            //json类型直接返回
            if (obj.getClass().getName().toUpperCase().contains("JSON")) {
                return obj.toString();
            }
            try {
                if (null != include) {
                    ObjectMapper objectMapper = newObjectMapper(include);
                    if (isPretty) {
                        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
                    }
                    return objectMapper.writeValueAsString(obj);
                } else if (isPretty) {
                    ObjectMapper result = new ObjectMapper();
                    result.enable(SerializationFeature.INDENT_OUTPUT);
                    return result.writeValueAsString(obj);
                }
                return JSON_MAPPER.writeValueAsString(obj);
            } catch (JsonProcessingException e) {
                log.error("", e);
            }
        }
        return null;
    }

    public static String object2json(Object obj, boolean isPretty) {
        return object2json(obj, null, isPretty);
    }

    public static String object2json(Object obj) {
        return object2json(obj, null, false);
    }

    public static <T> T map2Oject(Map<String, Object> data, Class<T> clazz) {
        return JSON_MAPPER.convertValue(data, clazz);
    }

    public static <T> T map2ObjectWithAnno(Map<?, ?> data, Class<T> clazz) {
        return JSON_MAPPER.convertValue(data, clazz);
    }

    public static <T> T json2Object(String content, Class<T> clazz) {
        try {
            return JSON_MAPPER.readValue(content, clazz);
        } catch (JsonProcessingException e) {
            log.error("", e);
        }
        return null;
    }

    public static Map<String, Object> json2Map(String json) {
        try {
            return json2Object(json, Map.class);
        } catch (Exception e) {
            log.error("", e);
        }
        return Maps.newHashMap();
    }

    public static Map<String, Object> json2Map(String json, TypeReference<Map<String, Object>> typeReference) {
        try {
            return JSON_MAPPER.readValue(json, typeReference);
        } catch (Exception e) {
            log.error("", e);
        }
        return Maps.newHashMap();
    }

    public static Map<String, Object> object2Map(Object obj) {
        try {
            return JSON_MAPPER.convertValue(obj, Map.class);
        } catch (Exception e) {
            log.error("", e);
        }
        return Maps.newHashMap();
    }

    public static <T> List<T> json2Array(String content, TypeReference<List<T>> typeReference) {
        try {
            return JSON_MAPPER.readValue(content, typeReference);
        } catch (JsonProcessingException e) {
            log.error("", e);
        }
        return Lists.newArrayList();
    }

    public static <T> List<T> json2Array(String content) {
        try {
            return JSON_MAPPER.readValue(content, new TypeReference<>() {
            });
        } catch (JsonProcessingException e) {
            log.error("", e);
        }
        return Lists.newArrayList();
    }

    public static void addArrayValue(JsonNode rootNode, String nodeName, String property, List<JsonNode> value) {
        JsonNode node = rootNode.findValue(nodeName);
        ObjectNode objectNode = (ObjectNode) node;
        ArrayNode arrayNode = objectNode.putArray(property);
        for (JsonNode o : value) {
            arrayNode.add(o);
        }
    }

    public static ArrayNode createArrayNode() {
        return JSON_MAPPER.createArrayNode();
    }

    public static ObjectNode createObjectNode() {
        return JSON_MAPPER.createObjectNode();
    }

    public static List<JsonNode> getJsonNodeParent(String content, String nodeName) {
        JsonNode root = readJson(content);
        assert root != null;
        return root.findParents(nodeName);
    }

    public static JsonNode readJson(String content) {
        try {
            return JSON_MAPPER.readTree(content);
        } catch (JsonProcessingException e) {
            log.error("", e);
        }
        return null;
    }

    public static void visitElements(JsonNode root, String property, List<String> values, FindNodeCallback callback) {
        visitElements(root, root, null, null, property, values, callback);
    }

    /**
     * 递归遍历JsonNode，查找符合条件的节点
     *
     * @param node     当前节点
     * @param callback 处理符合条件的节点集合
     * @param property 要匹配的键
     * @param values   要匹配的值
     */
    private static void visitElements(JsonNode root, JsonNode node, JsonNode parent, String nodeName, String property, List<String> values, FindNodeCallback callback) {
        if (node.isObject()) {
            if (node.has(property) && !values.contains(node.get(property).asText())) {
                callback.handleObjectNode(root, (ObjectNode) node, parent, nodeName);
            }
            node.fields().forEachRemaining(entry -> visitElements(root, entry.getValue(), node, entry.getKey(), property, values, callback));
        } else if (node.isArray()) {
            ArrayNode arrayNode = (ArrayNode) node;
            for (int i = 0; i < arrayNode.size(); i++) {
                JsonNode arrayItem = arrayNode.get(i);
                if (arrayItem.has(property) && !values.contains(arrayItem.get(property).asText())) {
                    callback.handleArrayNode(root, arrayNode, arrayItem, i);
                }
                visitElements(root, arrayItem, arrayNode, nodeName, property, values, callback);
            }
        }
    }

    private static ObjectMapper newObjectMapper(JsonInclude.Include include) {
        ObjectMapper result = new ObjectMapper();
        result.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        result.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        result.setSerializationInclusion(include);

        return result;
    }

    public static void traverse(JsonNode node, String targetName, String targetField, List<String> innerStopNames, List<String> urls) {
        if (node.has(TYPE_KEY) && innerStopNames != null && innerStopNames.contains(node.get(TYPE_KEY).asText())) {
            return;
        }
        if (node.has(targetName)) {
            JsonNode targetNode = node.get(targetName);
            if (targetNode.isTextual()) {
                urls.add(targetNode.asText());
            } else if (targetNode.isObject()) {
                if (targetNode.has(targetField)) {
                    urls.add(targetNode.get(targetField).asText());
                } else {
                    throw new RuntimeException("无法获取节点" + targetName + "的URL");
                }
            }
        }
        if (node.isObject()) {
            node.fields().forEachRemaining(entry -> traverse(entry.getValue(), targetName, targetField, innerStopNames, urls));
        } else if (node.isArray()) {
            node.forEach(element -> traverse(element, targetName, targetField, innerStopNames, urls));
        }

    }

    public interface FindNodeCallback {
        default void handleObjectNode(JsonNode root, ObjectNode node, JsonNode parent, String nodeName) {
        }

        default void handleArrayNode(JsonNode root, ArrayNode parent, JsonNode node, int index) {
        }

        default void handleTextNode(TextNode node, ObjectNode parent, String property) {
        }
    }

    public static class ListToStringDeserializer extends JsonDeserializer<String> {

        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            JsonNode node = p.getCodec().readTree(p);
            if (node.isArray() && !node.isEmpty()) {
                return node.toString();
            } else if (node.isTextual() && StringUtils.isNotBlank(node.asText())) {
                return node.asText();
            }
            return null;
        }
    }

    @Data
    @AllArgsConstructor
    static public class TraverseMatchRule {
        String property;
        boolean object;
    }
}
